import { assert, Node, Vec2 } from 'cc';
import { GComponent } from './GComponent';
import { InputProcessor } from './event/InputProcessor';
import { GObject } from './GObject';
import { GObjectPool, PopupDirection, UIObjectFactory, UIPackage } from './FairyGUI';
import { Event as FUIEvent } from './event/Event';

type ComponentClass<T> = (new() => T) & {URL: string};

interface IFairyGUIPop extends GComponent {
  onUIInit (): void;
  onUIUpdate (params: any): void;
  onUIHide (): void;
  onUIDestroy (): void;
}

type PopImpl<TP extends (TG & IFairyGUIPop), TG extends GComponent> = ComponentClass<TP>;

// 代替GRoot实现部分功能
export default class GPanel extends GComponent {
    private _inputProcessor: InputProcessor;
    private _popupStack: Array<GObject>;
    private _justClosedPopups: Array<GObject>;
    private _tooltipWin: GObject;

    private popObjectPools = new Map<string, GObjectPool>();
    private _isDesposing: boolean;

    public get isDesposing(): boolean {
        return this._isDesposing;
    }

    private getPopInstance<TP extends (TG & IFairyGUIPop), TG extends GComponent> (popCls: PopImpl<TP, TG>): {
        obj: TP,
        pool: GObjectPool,
    } {
      let popPool : GObjectPool;
      if (this.popObjectPools.has(popCls.URL)) {
        popPool = this.popObjectPools.get(popCls.URL);
      } else {
        popPool = new GObjectPool();
        this.popObjectPools.set(popCls.URL, popPool);
      }

      const isNew = popPool.count === 0;
      if (isNew) {
        UIObjectFactory.setExtension(popCls.URL, popCls);
      }
      const obj = popPool.getObject(popCls.URL) as TP;
      if (obj) {
        obj.name = obj.packageItem?.name || "GComponent-Unknow";
        if (isNew) obj.onUIInit();
        assert(obj.resourceURL === popCls.URL, "pop object resource url not match");
        return {obj, pool: popPool};
      } else {
        return null;
      }
    }

    private onUIObjectHide<TP extends (TG & IFairyGUIPop), TG extends GComponent>(item: TP, pool: GObjectPool): void {
        item.onUIHide && item.onUIHide();
        pool.returnObject(item);
    }

    public get inputProcessor(): InputProcessor {
        return this._inputProcessor;
    }

    public get touchTarget(): GObject {
        return this._inputProcessor.getTouchTarget();
    }

    constructor(node: Node) {
        super(node);
        this.name = 'GPanel';
        this._isRoot = true;
        this._popupStack = new Array<GObject>();
        this._justClosedPopups = new Array<GObject>();
    }

    public setInputProcessor(inputProcessor: InputProcessor): void {
        if (this._inputProcessor)
            this._inputProcessor._captureCallback = null;

        this._inputProcessor = inputProcessor;
        this._inputProcessor._captureCallback = this.onTouchBegin_1;
    }

    public getTouchPosition(touchId?: number): Vec2 {
        return this._inputProcessor.getTouchPosition(touchId);
    }

    public center(restraint?: boolean): void {
        // override do nothing
    }

    public getPopupPosition(popup: GObject, target?: GObject, dir?: PopupDirection, result?: Vec2): Vec2 {
        let pos = result || new Vec2();
        let sizeW = 0, sizeH = 0;
        if (target) {
            let x = 0, y = 0;
            if (target.pivotAsAnchor) {
                x = -target.width * target.pivotX;
                y = -target.height * (1 - target.pivotY);
            }
            pos = target.localToGlobal(x, y);
            this.globalToLocal(pos.x, pos.y, pos);
            let pos2 = target.localToGlobal(x + target.width, y + target.height);
            this.globalToLocal(pos2.x, pos2.y, pos2);
            sizeW = pos2.x - pos.x;
            sizeH = pos2.y - pos.y;
        }
        else {
            pos = this.getTouchPosition();
            pos = this.globalToLocal(pos.x, pos.y);
        }

        if (dir == undefined || dir == null || dir == PopupDirection.Auto) {
            if (pos.y + popup.height > this.height)
                dir = PopupDirection.Up;
            else
                dir = PopupDirection.Down;
        }

        if (target) {
            switch (dir) {
                case PopupDirection.Down:
                    pos.y += sizeH;
                    pos.x += sizeW / 2;
                    break;
                case PopupDirection.Up:
                    pos.x += sizeW / 2;
                    // pos.y -= popup.height;
                    break
                case PopupDirection.Left:
                    pos.y += sizeH / 2;
                    // pos.x -= popup.width;
                    break
                case PopupDirection.Right:
                    pos.x += sizeW;
                    pos.y += sizeH / 2;
                    break
                default:
                    break;
            }
        }

        // 以锚点对齐时
        if (popup.pivotAsAnchor) {
            const width = popup.width;
            const height = popup.height;
            const px = popup.pivotX;
            const py = popup.pivotY;
            const left = Math.max(px * width, 0);
            if (pos.x + left < 0)
                pos.x = left;
            const right = Math.min(width, (1 - px) * width);
            if (pos.x + right > this.width)
                pos.x = this.width - right;
            const top = Math.max(py * height, 0);
            if (pos.y + top < 0)
                pos.y = top;
            const bottom = Math.min(height, (1 - py) * height);
            if (pos.y + bottom > this.height)
                pos.y = this.height - bottom;
        } else {
            if (dir == PopupDirection.Up)
                pos.y -= popup.height;
            else if (dir == PopupDirection.Left)
                pos.x -= popup.width;

            if (pos.x < 0)
                pos.x = 0;
            if (pos.y < 0)
                pos.y = 0;
            if (pos.x + popup.width > this.width)
                pos.x = this.width - popup.width;
            if (pos.y + popup.height > this.height)
                pos.y = this.height - popup.height;
        }

        return pos;
    }

    public showFloating(item: GObject, target?: GObject | null, dir?: PopupDirection) {
        if (this._isDesposing ) return;
        if (target) {
            let p: GObject = target;
            while (p) {
                if (p.parent == this) {
                    if (item.sortingOrder < p.sortingOrder) {
                        item.sortingOrder = p.sortingOrder;
                    }
                    break;
                }
                p = p.parent;
            }
        }

        this.addChild(item);

        let pt = this.getPopupPosition(item, target, dir);
        item.setPosition(pt.x, pt.y);
    }

    public showPopup(popup: GObject, target?: GObject | null, dir?: PopupDirection): void {
        if (this._popupStack.length > 0) {
            let k: number = this._popupStack.indexOf(popup);
            if (k != -1) {
                for (let i: number = this._popupStack.length - 1; i >= k; i--)
                    this.removeChild(this._popupStack.pop());
            }
        }
        this._popupStack.push(popup);
        this.showFloating(popup, target, dir);
    }

    public togglePopup(popup: GObject, target?: GObject, dir?: PopupDirection): void {
        if (this._justClosedPopups.indexOf(popup) != -1)
            return;

        this.showPopup(popup, target, dir);
    }

    public hideAllPopup(url: string, onlySelf?: boolean): void {
        for (const element of this._popupStack) {
            if (element.resourceURL == url) {
                this.hidePopup(element, onlySelf);
            }
        }
    }

    public hidePopup(popup?: GObject, onlySelf?: boolean): void {
        if (popup) {
            let k: number = this._popupStack.indexOf(popup);
            if (k != -1) {
                if (onlySelf) {
                    this._popupStack.splice(k, 1);
                    this.closePopup(popup);
                } else {
                    for (var i: number = this._popupStack.length - 1; i >= k; i--)
                        this.closePopup(this._popupStack.pop());
                }
            }
        }
        else {
            let cnt: number = this._popupStack.length;
            for (i = cnt - 1; i >= 0; i--)
                this.closePopup(this._popupStack[i]);
            this._popupStack.length = 0;
        }
    }

    public get hasAnyPopup(): boolean {
        return this._popupStack.length != 0;
    }

    private closePopup(target: GObject): void {
        target.removeFromParent();
        const onHide = (<any>target).__onHide__;
        if (onHide && typeof onHide == 'function')
            onHide()
    }

    public showTooltips(url: string, msg: string, target?: GObject, dir?: PopupDirection) {
        this._tooltipWin = UIPackage.createObjectFromURL(url);
        if (!this._tooltipWin) return;
        this._tooltipWin.text = msg;
        this.showPopup(this._tooltipWin, target, dir);
    }

    public hideTooltips(): void {
        if (this._tooltipWin) {
            if (this._tooltipWin.parent)
                this.removeChild(this._tooltipWin);
            this._tooltipWin = null;
        }
    }

    private onTouchBegin_1(evt: FUIEvent): void {
        if (this._tooltipWin)
            this.hideTooltips();

        this._justClosedPopups.length = 0;
        if (this._popupStack.length > 0) {
            let mc: GObject = evt.initiator;
            while (mc && mc != this) {
                let pindex: number = this._popupStack.indexOf(mc);
                if (pindex != -1) {
                    for (let i: number = this._popupStack.length - 1; i > pindex; i--) {
                        var popup: GObject = this._popupStack.pop();
                        this.closePopup(popup);
                        this._justClosedPopups.push(popup);
                    }
                    return;
                }
                mc = mc.findParent();
            }

            let cnt: number = this._popupStack.length;
            for (let i: number = cnt - 1; i >= 0; i--) {
                popup = this._popupStack[i];
                this.closePopup(popup);
                this._justClosedPopups.push(popup);
            }
            this._popupStack.length = 0;
        }
    }

    showPop<TP extends (TG & IFairyGUIPop), TG extends GComponent> (popCls: PopImpl<TP, TG>
        , params?: any , onShow?: ()=>void, onHide?: ()=>void, onError?: (e: any)=>void
    ): TP {
        if (this._isDesposing ) return;
        const { obj, pool } = this.getPopInstance(popCls);
        if (!obj) {
            onError && onError(new Error(`pop object pool is empty: ${popCls.URL}`));
            return null;
        }
        this.showPopup(obj);
        obj.onUIUpdate(params);
        obj.center(true);
        (<any>obj)["__onHide__"] = () => {
            (<any>obj)["__onHide__"] = undefined;
            this.onUIObjectHide(obj, pool);
            onHide && onHide();
        };
        onShow && onShow();
        return obj;
    }

    hidePop<TP extends (TG & IFairyGUIPop), TG extends GComponent> (popInst?: TP|string): void {
      if (popInst && typeof popInst === "string") {
        this.hideAllPopup(popInst, true);
      } else if (popInst && popInst instanceof GObject) {
        this.hidePopup(popInst as GObject, true);
      } else {
        this.hidePopup();
      }
    }

    public clearPopPools (): void {
        this.popObjectPools.forEach((pool, url) => {
            for (let i = 0; i < pool.count; i++) {
                const obj = pool.getObject(url) as IFairyGUIPop;
                if (obj) {
                    obj.onUIDestroy();
                    obj.dispose();
                }
            }
            pool.clear();
        });
    }

    public dispose(): void {
        this._isDesposing = true;
        this.hidePopup();
        this.clearPopPools();
        super.dispose();
    }
}